/******************************************************************************
 cnome: A C++ source "genes" discover. :-)

 See more details at:

   http://www.codeplex.com/cnome

 The original author site is at: http://www.alexandre-barbosa.com/

 Copyright (c) 2008 Alexandre Barbosa.
 Distributed under the MIT License. (See accompanying file LICENSE-MIT.txt 
 or at http://www.codeplex.com/cnome/license)
 ******************************************************************************/

#if defined(_MSC_VER) && (_MSC_VER >= 1400)
#pragma warning ( disable : 4996 )
#pragma warning ( disable : 4800 )
#pragma warning ( disable : 4355 )
#endif

#include <string>

#include "scopes.h"

#include "cnome_iterator.h"
#include "symbol_item.h"
#include "util.h"
#include "variables.h"

using namespace std;
using namespace cnome::util;

namespace cnome { namespace scopes {
bool is_builtin(const string& v) {
// Verify if v has "const" at the end
if (v.size() > 5 && v.substr(v.size()-5) == "const")
    return    v == "char const"
           || v == "unsigned char const"
           || v == "signed char const"
           || v == "wchar_t const"
           || v == "bool const"
           || v == "short const"
           || v == "unsigned short const"
           || v == "signed short const"
           || v == "int const"
           || v == "short int const"
           || v == "unsigned short int const"
           || v == "signed short int const"
           || v == "unsigned int const"
           || v == "signed int const"
           || v == "long const"
           || v == "unsigned long const"
           || v == "signed long const"
           || v == "long int const"
           || v == "unsigned long int const"
           || v == "signed long int const"
           || v == "long long const"
           || v == "unsigned long long const"
           || v == "signed long long const"
           || v == "long long int const"
           || v == "unsigned long long int const"
           || v == "signed long long int const"
           || v == "signed const"
           || v == "unsigned const"
           || v == "float const"
           || v == "double const"
           || v == "long double const"
           || v == "void const"
           || v == "...";

    return    v == "char"
           || v == "unsigned char"
           || v == "signed char"
           || v == "wchar_t"
           || v == "bool"
           || v == "short"
           || v == "short int"
           || v == "unsigned short"
           || v == "signed short int"
           || v == "unsigned short int"
           || v == "signed short"
           || v == "int"
           || v == "unsigned int"
           || v == "signed int"
           || v == "long"
           || v == "unsigned long"
           || v == "signed long"
           || v == "long int"
           || v == "unsigned long int"
           || v == "signed long int"
           || v == "long long"
           || v == "unsigned long long"
           || v == "signed long long"
           || v == "long long int"
           || v == "unsigned long long int"
           || v == "signed long long int"
           || v == "signed"
           || v == "unsigned"
           || v == "float"
           || v == "double"
           || v == "long double"
           || v == "void";
}

// TODO: Handle builtin and method parameters properly.
// TODO: Handle pointer to function properly. Ex: void (*)(void)
// TODO: Handle val == ""
scopesFromStr::scopesFromStr(const string& v, size_t suffixSize):m_first_time(true), m_value(v), m_idx(0),
        m_suffixSize(suffixSize), m_idxLstItSbls(var::l_usgDrts.begin()) {
    if (m_suffixSize == string::npos) {
        m_suffixSize = var::scopeSuffixSize;
    } else if (m_suffixSize == 0 || m_suffixSize > m_value.size()) {
        m_suffixSize = m_value.size();
    }
}

const string& scopesFromStr::current_scope()const{ return m_scope; }

size_t scopesFromStr::find_right_parenthesis(size_t leftParentIdx) {
    size_t auxRPrtIdx = leftParentIdx;
    int insideParameter = 1;
    for (auxRPrtIdx = leftParentIdx+1; auxRPrtIdx < m_value.size(); auxRPrtIdx++) {
        if (m_value[auxRPrtIdx] == ')')
            insideParameter--;
        else if (m_value[auxRPrtIdx] == '(')
            insideParameter++;
        if (!insideParameter)
            break;
    }
    return !insideParameter?auxRPrtIdx:string::npos;
}

void scopesFromStr::advance_idx() {
    size_t idx2 = m_idx;
    size_t idx3;
    size_t idxLP;
    size_t idxRP;
    do {
        if (idx2 != string::npos && m_value.size() > 1 && idx2 < m_value.size()-2)
            idx3 = m_value.find_first_of(":$", idx2+(m_value[idx2]==':'&&m_value[idx2+1]==':'?2:1));
        else {
            m_idx = string::npos;
            return;
        }

        // Verify if idx3 is inside a parameter list
        for (idxLP = idx2, idxRP = m_idx;    idxLP != string::npos && idxLP + 1 < m_value.size() 
            && idxRP != string::npos && idxRP+1 < m_value.size() 
            && idxLP < idx3;) {
            idxLP = m_value.find('(', idxLP+1);
            while (     idxLP != string::npos              // Verify if it is a pointer to function
                && (    m_value.substr(idxLP, 3) == "(*)"       
                || m_value.substr(idxLP, 8) == "(*const)" 
                )    
                )
                idxLP = m_value.find('(', idxLP + 1);        // Skip it
            idxRP = find_right_parenthesis(idxLP);
            // Begin: FIX Bug 10 - scopesFromStr returning wrong subscopes
            // ALB - 31may06
            while (    idxLP != string::npos && idxRP != string::npos
                && (idxLP <= idx3 && idx3 <= idxRP) )
                idx3 = m_value.find_first_of(":$", idx3+(m_value[idx3]==':'&&m_value[idx3+1]==':'?2:1));
            // End: FIX Bug 10
        } 
        idx2 = idx3;
    }while (    idx2 != string::npos && idxLP != string::npos && idxRP != string::npos 
        && (idxLP <= idx2 && idx2 <= idxRP) );
    m_idx = idx2;
    if (m_idx > m_value.size() - m_suffixSize) // Is index inside suffix?
        m_idx = string::npos;
}

const string& scopesFromStr::next_scope() {
    if (m_idx != m_value.npos || m_first_time) {
        if (m_idx == m_value.npos) {
            m_scope = m_value;
            m_first_time = false;
        }
        else {
            size_t incIdx = m_value[m_idx]==':'?2:m_value[m_idx]=='$'?1:0;
            if (m_idx+incIdx <= m_value.size()-m_suffixSize)
                m_scope = m_value.substr(0,m_idx+incIdx)+m_value.substr(m_value.size()-m_suffixSize);
            else
                m_scope = m_value.substr(m_value.size()-m_suffixSize);
            advance_idx();
            if (m_idx == string::npos)
                m_first_time = false;
        }
        if (m_scope.size()&&!is_builtin(m_scope)&&m_scope!="...") {
            if (m_scope[0] == '$') {
                if (m_scope.size() > 1)
                    m_scope = m_scope.substr(1);
                else
                    m_scope = "";
            }
            if (m_scope[0] != ':')
                m_scope = "::" + m_scope;
        }
    }
    else if (m_idxLstItSbls != var::l_usgDrts.end()) {
        m_scope = (*m_idxLstItSbls)->second->to_str() + ( getSuffixForSymbol(*(*m_idxLstItSbls)->second) + m_value.substr(m_value.size() - m_suffixSize));
        ++m_idxLstItSbls;
    }
    else m_scope = "";
    
    return m_scope;
}

scopesFromScp::scopesFromScp(const symbolItemPtr& v, size_t suffixSize )
: m_sbl(v), m_suffixSize(suffixSize), m_value(m_sbl?m_sbl->to_str():"")
{}

const std::string& scopesFromScp::currentScope()const {
    return m_value;
}

const std::string& scopesFromScp::nextScope() {
    if (m_sbl) {
        if (strlen(m_sbl->to_str()) <= m_suffixSize)
            m_value = "";
        else {
            const_cast<symbolItemPtr&>(m_sbl) = m_sbl->get_scope();
            m_value = m_sbl?m_sbl->to_str():"";
        }
    }

    return m_value;
}

inline std::string& get_scope(scopeT& sbl, std::string& scp, bool restart = true) {
    if (sbl.size()) {
        if (sbl.back()->get_symbol_type() == symbolItem::keyword)
            scp = sbl.back()->to_str();
        else
            scp += sbl.back()->to_str();
        appendAdorners(scp, sbl.back()->get_adorners());
    }
    return scp;
}

std::string& get_scope(std::string& scp, const char* tk) {
    itSymbolItem it;
    if (    tk 
         && (it = cnome_symbols.find(tk)) != cnome_symbols.end() 
         && it->second->get_symbol_type() == symbolItem::keyword
       )
           return scp = tk;

    get_scope(var::scope, scp);
    if (tk) {
        if (isCtorDeclWithoutParent(scp)) // TODO: Verify why enterscope for a::a isn't recognized the second a as a constructor
            scp = scp + '$' + tk;
        else if (var::scope.size())
            scp = scp + getSuffixForSymbol(*var::scope.back()) + tk;
        else 
            scp = scp + getSuffixForSymbol(smb_FIRST_TOKEN) + tk;
    }
    return scp;
}

// TODO: Study the use of useCurScope parameter bellow. 
std::string& get_scope(std::string& scp, scopeT& tk, bool useCurScope) {
    if (useCurScope)
        get_scope(scp);
    get_scope(tk, scp, false);

    return scp;
}

std::string& get_scope(std::string& scp, scopeT& tk, size_t& suffixSize, bool useCurScope) {
    size_t auxSz = 0;
    if (useCurScope) {
        get_scope(scp);
        auxSz = scp.size();
    }
    get_scope(tk, scp, false);
    
    itSymbolItem it;
    if (    (it = cnome_symbols.find(scp)) != cnome_symbols.end() 
         && it->second->get_symbol_type() == symbolItem::keyword
       )
           auxSz=0;
    suffixSize = (scp.size() >= auxSz)?scp.size()-auxSz:0;

    return scp;
}

std::string& get_previous_scope(std::string& scp) {
    if (!var::l_scope.size())
        return scp;  // No previous scope

    return get_scope(var::l_scope.back(), scp, false);
}

// TODO: Describe and find a better name to method bellow.
void strToScope(const string curScp, const string& val, string& auxScope, cnome_iterator& first) {
    string auxPrefix = curScp.substr(0, curScp.rfind(val));
    int insideParameters = 0;
    for (std::size_t idx = 0; idx < val.size(); idx++) {
        if (val[idx] == '(') {
            insideParameters++;
            continue;
        }
        else if (val[idx] == ')') {
            insideParameters--;
            continue;
        }
        else if ((val[idx] == ':' || val[idx] == '$') && insideParameters == 0) {
            auxScope = val.substr(0,idx);
            auxScope = auxPrefix + auxScope;
            itSymbolItem it;
            if ((it=cnome_symbols.find(auxScope)) != cnome_symbols.end())
                var::scope.push_back(it->second);
            else {
                token_t tk = *first;
                tk.set_value(auxScope.c_str());
                symbolItemPtr tmp = symbolItemPtr(new symbolItem(tk, symbolItem::other, first.get_cnome_position()));
                var::scope.push_back(tmp);
            }
            if (val[idx] == ':' && val[idx+1] == ':')
                idx++;
        }
    }
    auxScope = val;
    auxScope = auxPrefix + auxScope;
}

const itSymbolItem get_symbol_scope(const char* scp) {
    if (!scp || !*scp)  // Is scp a valid pointer or empty?
        return cnome_symbols.end();

    int idx = strlen(scp) - 1;
    if (idx > 4 && !strcmp(scp+idx-4, "const")) // Handle special case "int a::ab(void)const"
        idx-=5;
    if ( scp[idx] == ')') {
        int parentMatch;
        for (parentMatch = 1, idx--; parentMatch && idx >= 0; idx--)
            if (scp[idx] == '(')
                parentMatch--;
            else if (scp[idx] == ')')
                parentMatch++;
    }
    for(;idx>0&&scp[idx-1]!=':'&&scp[idx-1]!='$';idx--);
    for(;idx>0&&(scp[idx-1]==':'||scp[idx-1]=='$');idx--);
    if (!idx)
        return cnome_symbols.end();
    else {
        itSymbolItem ret;
        char tmp = scp[idx];
        const_cast<char*>(scp)[idx] = '\0';
        ret = cnome_symbols.find(scp);
        const_cast<char*>(scp)[idx]=tmp;
        return ret;
    }
}

void enter_scope(std::string& val, cnome_iterator& first) {
#if defined(CNOME_DEBUG)
    string prevScp;
    get_scope(prevScp);
#endif
    string oldScp;
    get_scope(oldScp,val.c_str());

    std::string auxPrefix = oldScp.substr(0,oldScp.rfind(val));

    string auxScope;
    strToScope(oldScp, val, auxScope, first);

    itSymbolItem it = cnome_symbols.end();
    scopesFromStr scopes(auxScope, val.size());
    while (scopes.next_scope().size()) {
        itSymbolItem it2;
        if ((it2=cnome_symbols.find(scopes.current_scope())) != cnome_symbols.end())
            it = it2;
    }
    symbolItemPtr smb;
    if (it != cnome_symbols.end())
        smb = it->second;
    else {
        token_t tk = *first;
        tk.set_value(val.c_str());
        smb = symbolItemPtr(new symbolItem(tk, symbolItem::other, first.get_cnome_position()));
        itSymbolItem it;
        if ((it=get_symbol_scope(oldScp.c_str())) != cnome_symbols.end())
            smb->set_scope(it->second);
		else if (var::scope.size())
			smb->set_scope(var::scope.back());
    }
    var::scope.push_back(smb);

#if defined(CNOME_DEBUG)
    string newScp;
    get_scope(newScp);
    std::cout << "enter_scope: val = " << val << endl;
    std::cout << "enter_scope: " << newScp << " from " << prevScp << endl;
#endif
}
}} // namespace cnome { namespace scopes {

#if defined(_MSC_VER) && (_MSC_VER >= 1400)
#pragma warning ( default : 4996 )
#pragma warning ( default : 4800 )
#pragma warning ( default : 4355 )
#endif
